ATOM Documentation

← Back to App

Integration System Documentation

Overview

The ATOM SaaS platform supports 569+ integrations through a hybrid approach:

  • **Native Implementations**: High-performance, first-class integrations with full feature support
  • **Activepieces Universal Adapter**: Instant access to 569+ integrations without native code

---

Database Schema

OAuth States Table

Stores temporary OAuth state tokens for CSRF protection.

CREATE TABLE oauth_states (
  id VARCHAR PRIMARY KEY,
  tenant_id VARCHAR NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  user_id VARCHAR REFERENCES users(id) ON DELETE CASCADE,
  state VARCHAR UNIQUE NOT NULL,
  provider VARCHAR NOT NULL,
  expires_at TIMESTAMPTZ NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  redirect_uri VARCHAR,
  extra_data JSON
);

Integration Tokens Table

Stores OAuth credentials and API keys for all integrations.

CREATE TABLE integration_tokens (
  id VARCHAR PRIMARY KEY,
  tenant_id VARCHAR NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  user_id VARCHAR REFERENCES users(id) ON DELETE CASCADE,
  workspace_id VARCHAR REFERENCES workspaces(id) ON DELETE SET NULL,
  provider VARCHAR NOT NULL,
  access_token TEXT NOT NULL,
  refresh_token TEXT,
  token_type VARCHAR DEFAULT 'Bearer',
  expires_at TIMESTAMPTZ,
  instance_url TEXT,
  scope TEXT,
  status VARCHAR DEFAULT 'active',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(tenant_id, provider, user_id)
);

---

Native Integrations

Implemented Integrations

Tier 1: Critical (Week 2-3)

ProviderCategoryAuth TypeStatus
StripeE-commerceOAuth2✅ Complete
Google DriveProductivityOAuth2✅ Complete
TrelloProductivityOAuth2✅ Complete
AsanaProductivityOAuth2✅ Complete
LinearDevelopmentOAuth2✅ Complete
AirtableProductivityAPI Key✅ Complete

Previously Implemented (13 integrations)

  • Slack, Salesforce, HubSpot, Gmail, Outlook, Teams, Notion, Jira, GitHub, Shopify, Zoom, Zoho, Google Calendar

**Total Native Integrations: 19**

---

Integration Client Pattern

All native integrations follow this structure:

import { getDatabase } from '@/lib/db';

export interface {Provider}Tokens {
  access_token: string;
  refresh_token?: string;
  // ... provider-specific fields
}

export class {Provider}Client {
  private tenantId: string;
  private userId?: string;
  private tokens: {Provider}Tokens | null = null;

  constructor(tenantId: string, userId?: string) {
    this.tenantId = tenantId;
    this.userId = userId;
  }

  // OAuth URL generation
  static getAuthUrl(state: string, scopes?: string[]): string {
    const config = getConfig();
    const params = new URLSearchParams({
      client_id: config.clientId,
      redirect_uri: config.redirectUri,
      response_type: 'code',
      scope: scopes.join(' '),
      state,
    });
    return `{AUTH_URL}?${params}`;
  }

  // Token exchange
  static async exchangeCode(code: string): Promise<{Provider}Tokens> {
    const config = getConfig();
    const response = await fetch(`{TOKEN_URL}`, {
      method: 'POST',
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: config.clientId,
        client_secret: config.clientSecret,
        redirect_uri: config.redirectUri,
        code,
      }),
    });
    return response.json();
  }

  // Load from database
  async loadTokens(): Promise<boolean> {
    const db = getDatabase();
    const result = await db.query(
      `SELECT access_token, refresh_token FROM integration_tokens
       WHERE tenant_id = $1 AND provider = '{provider}'
       AND status = 'active' LIMIT 1`,
      [this.tenantId]
    );
    if (result.rows.length === 0) return false;
    this.tokens = result.rows[0];
    return true;
  }

  // Save to database
  async saveTokens(tokens: {Provider}Tokens): Promise<void> {
    const db = getDatabase();
    await db.query(
      `INSERT INTO integration_tokens (tenant_id, provider, access_token, refresh_token, status)
       VALUES ($1, '{provider}', $2, $3, 'active')
       ON CONFLICT (tenant_id, provider)
       DO UPDATE SET access_token = EXCLUDED.access_token,
         refresh_token = COALESCE(EXCLUDED.refresh_token, integration_tokens.refresh_token),
         updated_at = NOW()`,
      [this.tenantId, tokens.access_token, tokens.refresh_token]
    );
  }

  // Provider-specific API methods
  async getItems(): Promise<any[]> { /* ... */ }
}

---

API Routes

Connect Endpoint

GET /api/integrations/{provider}?action=connect

**Response:**

{
  "authUrl": "https://provider.com/oauth/authorize?...",
  "state": "random_state_token"
}

Callback Endpoint

GET /api/integrations/{provider}/callback?code=...&state=...

**Response:**

Redirects to /integrations?success={provider} or /integrations?error=...

Status Endpoint

GET /api/integrations/{provider}?action=status

**Response:**

{
  "connected": true,
  "connection": {
    "id": "token_id",
    "scope": "read_write",
    "connected_at": "2025-02-01T00:00:00Z",
    "updated_at": "2025-02-01T00:00:00Z"
  }
}

Disconnect Endpoint

GET /api/integrations/{provider}?action=disconnect

**Response:**

{
  "success": true,
  "message": "{Provider} disconnected"
}

---

Activepieces Universal Adapter

For integrations without native implementation, use the Activepieces adapter:

import { createActivepiecesAdapter } from '@/lib/integrations/activepieces-adapter';

const adapter = createActivepiecesAdapter(tenantId, userId);

// Execute any piece
const result = await adapter.executePiece({
  pieceId: 'discord',
  action: 'send_message',
  config: { webhook_url: '...' },
  input: { content: 'Hello from ATOM!' },
});

// List available integrations
const pieces = adapter.listPieces('communication');

// Search integrations
const results = adapter.searchPieces('crm');

Supported Categories

  • Communication (Slack, Discord, Telegram, Twilio, SendGrid, ...)
  • Productivity (Notion, Trello, Asana, Monday, ClickUp, ...)
  • CRM (Salesforce, HubSpot, Pipedrive, Zoho, ...)
  • E-commerce (Shopify, Stripe, WooCommerce, Square, ...)
  • Storage (Google Drive, Dropbox, OneDrive, Box, ...)
  • Development (GitHub, GitLab, Linear, Jira, ...)
  • Marketing (Mailchimp, ConvertKit, ...)
  • Accounting (QuickBooks, Xero, FreshBooks, ...)
  • AI/ML (OpenAI, Anthropic, Hugging Face, ...)

**Total: 569+ integrations**

---

Environment Variables Pattern

All integrations follow this naming convention:

# OAuth2 Integrations
{PROVIDER}_CLIENT_ID=""
{PROVIDER}_CLIENT_SECRET=""
{PROVIDER}_REDIRECT_URI="https://your-domain.com/api/integrations/{provider}/callback"

# API Key Integrations
{PROVIDER}_API_KEY=""

**Example:**

STRIPE_CLIENT_ID="ca_..."
STRIPE_CLIENT_SECRET="sk_..."
STRIPE_REDIRECT_URI="https://atom.ai/api/integrations/stripe/callback"

AIRTABLE_API_KEY="pat_..."

---

Multi-Tenancy

All integration operations MUST include tenant context:

// ✅ CORRECT - Multi-tenant
const db = getDatabase();
const result = await db.query(
  `SELECT * FROM integration_tokens
   WHERE tenant_id = $1 AND provider = $2`,
  [tenantId, provider]
);

// ❌ WRONG - Missing tenant filter
const result = await db.query(
  `SELECT * FROM integration_tokens
   WHERE provider = $1`,
  [provider]
);

Row-Level Security (RLS) is enabled on both oauth_states and integration_tokens tables.

---

Token Refresh Automation

OAuth2 access tokens are automatically refreshed:

private async request<T>(endpoint: string): Promise<T> {
  // Load tokens
  if (!this.tokens) await this.loadTokens();

  // Check if expired
  if (this.tokens.expires_at) {
    const expiresAt = new Date(this.tokens.expires_at);
    if (Date.now() >= expiresAt.getTime() && this.tokens.refresh_token) {
      // Refresh token
      this.tokens = await {Provider}Client.refreshAccessToken(this.tokens.refresh_token);
      await this.saveTokens(this.tokens);
    }
  }

  // Make request
  return fetch(url, {
    headers: { Authorization: `Bearer ${this.tokens.access_token}` },
  });
}

---

Code Generator

Generate new integrations automatically:

npm run generate:integration -- --provider stripe --auth oauth2
npm run generate:integration -- --provider airtable --auth api_key
npm run generate:integration -- --provider figma --auth basic

The generator creates:

  1. Client library: src/lib/integrations/{provider}.ts
  2. API route: src/app/api/integrations/{provider}/route.ts
  3. Callback route: src/app/api/integrations/{provider}/callback/route.ts
  4. Environment variables: Updates .env.example

---

Testing

Test OAuth Flow

# 1. Start dev server
npm run dev

# 2. Navigate to /integrations

# 3. Click "Connect {Provider}"

# 4. Complete OAuth flow in provider popup

# 5. Verify redirect with ?success={provider}

# 6. Check database
psql $DATABASE_URL -c "SELECT * FROM integration_tokens WHERE provider = '{provider}'"

Test API Call

const client = new StripeClient(tenantId);
await client.loadTokens();
const balance = await client.getBalance();
console.log(balance);

---

Security

CSRF Protection

All OAuth flows use state tokens stored in oauth_states table with 10-minute expiration.

Token Encryption

Access and refresh tokens should be encrypted at rest (implementation needed).

RLS Policies

Row-Level Security ensures tenant isolation:

CREATE POLICY integration_tokens_tenant_isolation
ON integration_tokens
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);

---

Troubleshooting

Issue: "Invalid state" error

**Cause:** OAuth state expired or invalid

**Fix:** Check oauth_states table, ensure state was created within 10 minutes

Issue: "Token exchange failed"

**Cause:** Invalid client credentials or callback URL mismatch

**Fix:** Verify CLIENT_ID, CLIENT_SECRET, and REDIRECT_URI match provider app settings

Issue: "Provider not connected"

**Cause:** No active tokens in database

**Fix:** Check integration_tokens table for provider and tenant

---

Roadmap

Week 1: Foundation ✅

  • [x] Database migration
  • [x] SQLAlchemy models
  • [x] Code generator

Week 2-3: Tier 1 ✅

  • [x] Stripe
  • [x] Google Drive
  • [x] Trello
  • [x] Asana
  • [x] Linear
  • [x] Airtable

Week 4-5: Tier 2 (Planned)

  • [ ] Monday.com
  • [ ] ClickUp
  • [ ] Dropbox
  • [ ] OneDrive
  • [ ] QuickBooks
  • [ ] Xero
  • [ ] Pipedrive

Week 6-7: Tier 3 (Planned)

  • [ ] Discord
  • [ ] Twilio
  • [ ] WhatsApp
  • [ ] Intercom
  • [ ] SendGrid
  • [ ] Figma
  • [ ] Box
  • [ ] Square
  • [ ] Plaid

Week 8-10: Activepieces (Complete)

  • [x] Universal adapter
  • [ ] Catalog UI
  • [ ] Dynamic execution engine

---

Integration Status Dashboard

To view integration status:

GET /api/integrations/status

**Response:**

{
  "total_integrations": 569,
  "native_implementations": 19,
  "connected": 8,
  "categories": 9,
  "providers": [
    {
      "id": "slack",
      "name": "Slack",
      "connected": true,
      "status": "active"
    },
    // ...
  ]
}

---

Contributing

To add a new integration:

  1. **Use the generator:**
  1. **Add to catalog:**
  1. **Update .env.example:**
  1. **Test:**
  1. **Document:**

---

Support

For integration-specific questions:

  • **Slack**: https://api.slack.com/docs
  • **Stripe**: https://stripe.com/docs/api
  • **Activepieces**: https://www.activepieces.com/docs

For platform issues:

  • Check logs: tail -f backend-saas/logs/app.log
  • Database: Verify RLS policies are active
  • Environment: Confirm all variables are set